package test.lesson

/**
 * author:vesper.z
 * date: 2024  17:24
 * class: Test10
 * description：泛型 ，即"参数化类型"，将类型参数化，可以用在类、接口、方法上。
 *              跟Java一样，Kotlin也提供泛型，为类型安全提供保障，消除类型强转的烦恼。
 **/
class Test10 {
}

/**
 * 声明一个泛型类
 */
class Maybe<T>(t: T) {
    var value = t
}

//泛型类测试方法1
fun test1() {
    var m1: Maybe<Int> = Maybe<Int>(123)
    var m2 = Maybe<Char>('a')
    var m3: Maybe<Int> = Maybe(2)//编译器会自动推断类型，编译器知道该类型是Maybe<Int>
    var m4 = Maybe("m4")//编译器会自动推断类型，编译器知道该类型是Maybe<String>

    println("m1.value ->${m1.value}")
    println("m2.value ->${m2.value}")
    println("m3.value ->${m3.value}")
    println("m4.value ->${m4.value}")
}

/**
 * 泛型函数
 * 定义泛型类型变量，可以完整地写明类型参数，如果编译器可以自动推定类型参数，也可以省略类型参数。
 * Kotlin泛型函数的声明与Java相同，类型参数要放到函数名的前面：
 */
fun <T> maybeInit(t: T) = Maybe("123")
fun <T> maybeShowType(t: T) {
    when (t) {
        is String -> println("current type is String")
        is Int -> println("current type is Int")
        is Char -> println("current type is Char")
        else -> println("current type is other")
    }
}

//泛型函数测试方法2
fun test2() {
    var m1: Maybe<String> = maybeInit("123")
    var m2 = maybeInit(123)//编译器会根据类型自动推断
    println(m1.value)
    println(m2.value)
    maybeShowType("123")
    maybeShowType(123)
    maybeShowType(arrayOf(123))
}

/**
 * 泛型约束
 * 使用泛型约束来设定一个给定参数允许使用的类型。
 * Kotlin中使用":"对泛型的上限进行约束
 * 最常见的约束是上界(upper bound)
 */
fun <T : Collection<*>> processCollection(t: T) {
    println(t.toString())
}

fun <T : Comparable<T>> sort(list: List<T>) {
}

//泛型约束的测试方法
fun test3() {
    processCollection(listOf(1, 2, 3, 4))
    sort(listOf(1, 2, 3))
}

/**
 *型变
 *Kotlin 中没有通配符类型，它有两个其他的东西：声明处型变（declaration-site variance）与类型投影（type projections）。
 */

/**
 * 声明处型变
 * 声明处的类型变异使用协变注解修饰符:in、out，消费者in，生产者out
 * 使用out使得一个类型参数协变，协变类型参数只能用作输出，可以作为返回类型但是无法作为入参的类型：
 */
//定义一个支持协变类型的类
class MChangeOut<out A>(val a: A) {
    fun foo(): A {
        return a
    }
}

/**
 * in使得一个参数的类型逆变，逆变类型参数只能用作输入，可以作为参数的类型但是无法作为返回值的类型：
 * 如果强制返回的话，报错如下：
 * Kotlin: Type parameter T is declared as 'in' but occurs in 'out' position in type T
 */
//定义一个支持逆变的类
class MChangeIn<in T>(a: T) {
    fun foo(a: T) {
        println(a)
    }
}

//声明处型变测试方法
fun test4() {
    var mChangeOut = MChangeOut<String>("123")
    var foo = mChangeOut.foo()
    println(foo)
    var mChangeIn = MChangeIn<String>("123")
    mChangeIn.foo("123")

}

/**
 *  星号投射
 *  有些时候, 你可能想表示你并不知道类型参数的任何信息, 但是仍然希望能够安全地使用它.
 *  这里所谓"安全地使用"是指, 对泛型类型定义一个类型投射, 要求这个泛型类型的所有的实体实例, 都是这个投射的子类型。
 * 对于这个问题, Kotlin 提供了一种语法, 称为 星号投射(star-projection):
 *
 * 假如类型定义为 Foo<out T>,其中T是一个协变的类型参数, 上界(upper bound)为 TUpper ,Foo<>等价于Foo<out TUpper>.它表示,当T未知时,你可以安全地从Foo<>中读取TUpper类型的值.
 * 假如类型定义为 Foo<in T>,其中T是一个反向协变的类型参数,Foo<>等价于Foo<inNothing>.它表示,当T未知时,你不能安全地向 Foo<>写入任何东西.
 * 假如类型定义为 Foo<T>,其中T是一个协变的类型参数,上界(upper bound)为TUpper,对于读取值的场合,Foo<*>等价于Foo<out TUpper>,对于写入值的场合,等价于Foo<in Nothing> .
 */

fun main(args: Array<String>) {
    //测试泛型
    println("###############test1##################")
    test1()
    println("###############test2##################")
    test2()
    println("###############test3##################")
    test3()
    println("###############test4##################")
    test4()

}